package com.lagou.rpc.provider.handler;

import com.alibaba.fastjson.JSON;
import com.lagou.rpc.common.RpcRequest;
import com.lagou.rpc.common.RpcResponse;
import com.lagou.rpc.provider.anno.RpcService;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.springframework.beans.BeansException;
import org.springframework.cglib.reflect.FastClass;
import org.springframework.cglib.reflect.FastMethod;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 业务处理类
 * 1。将标有@RpcService注解的bean缓存（当有远程调用的时候，从缓存中获取对外暴露的服务端处理类）
 *      从容器中获取注解，需要获取容器对象
 *          实现ApplicationContextAware，实现其中的setApplicationContext接口，找到标有@RpcService的bean
 * 2。接收客户端请求
 * 3。根据传递过来的beanName从缓存中查找对应的bean
 * 4。解析请求中的方法名称、参数类型、参数信息
 * 5。通过反射调用bean的方法
 * 6。给客户端进行响应
 */
@Component
@ChannelHandler.Sharable
public class RpcServerHandler extends SimpleChannelInboundHandler<String> implements ApplicationContextAware {

    private static final Map SERVICE_INSTANCE_MAP = new ConcurrentHashMap();

    /**
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //1。将标有@RpcService注解的bean缓存
        //通过注解查找bean
        Map<String, Object> serviceMap = applicationContext.getBeansWithAnnotation(RpcService.class);
        if (serviceMap.size() > 0){
            Set<Map.Entry<String, Object>> entries = serviceMap.entrySet();
            for (Map.Entry<String, Object> item : entries) {
                Object serviceBean = item.getValue();   //得到UserServiceImpl
                //判断上面得到的对象有没有实现接口，因为客户端调用服务端是基于接口的协议的
                if (serviceBean.getClass().getInterfaces().length == 0){
                    throw new RuntimeException("服务必须实现接口");
                }
                //有可能实现多个接口，默认取第一个实现的接口作为缓存bean的名称
                String beanName = serviceBean.getClass().getInterfaces()[0].getName();
                SERVICE_INSTANCE_MAP.put(beanName, serviceBean);
            }
        }

    }

    /**
     * 通道读取就绪事件
     * @param channelHandlerContext
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
        //2。接收客户端请求，msg是json字符串，将msg转换为RpcRequest对象
        RpcRequest rpcRequest = JSON.parseObject(msg, RpcRequest.class);
        RpcResponse rpcResponse = new RpcResponse();
        rpcResponse.setRequestId(rpcRequest.getRequestId());
        try {
            //业务处理
            rpcResponse.setResult(handler(rpcRequest));
        } catch (Exception e){
            e.printStackTrace();
            rpcResponse.setError(e.getMessage());
        }

        //6。给客户端进行响应
        channelHandlerContext.writeAndFlush(JSON.toJSONString(rpcResponse));
    }

    /**
     * 业务处理逻辑
     * @return
     */
    public Object handler(RpcRequest rpcRequest) throws InvocationTargetException {
        //3。根据传递过来的beanName从缓存中查找对应的bean
        Object serviceBean = SERVICE_INSTANCE_MAP.get(rpcRequest.getClassName());
        if (serviceBean == null){
            throw new RuntimeException("根据beanName[" + rpcRequest.getClassName() + "]找不到服务");
        }
        //4。解析请求中的方法名、参数类型、参数信息
        Class<?> serviceBeanClass = serviceBean.getClass();   //得到serviceBean的类型
        String methodName = rpcRequest.getMethodName(); //从rpcRequest中得到调用的方法名
        Class<?>[] parameterTypes = rpcRequest.getParameterTypes(); //从rpcRequest中得到参数类型
        Object[] parameters = rpcRequest.getParameters(); //从rpcRequest中得到参数

        //5。反射调用bean的方法-CGLIB反射调用
        FastClass fastClass = FastClass.create(serviceBeanClass);
        FastMethod method = fastClass.getMethod(methodName, parameterTypes);

        return method.invoke(serviceBean, parameters);
    }

}
